package com.zonski.jbots.engine;

import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;

import com.zonski.jbots.engine.entity.Mind;
import com.zonski.jbots.engine.entity.MovingEntity;

/**
 * Responsible for managing the interactions of individual entities
 * in the game
 */
public class Engine
{
    /**
     * The maximum number of collision iterations that we'll
     * perform before giving up
     */
    private static final int MAX_COLLISIONS = 3;

    private QuickVector cachedCollisions = new QuickVector(5);
    private QuickVector collisionVector = new QuickVector(5);
    private Rectangle collisionRect = new Rectangle(0, 0, 0, 0);
    private Rectangle tileRect = new Rectangle(0, 0, 0, 0);

    private static final String PLAYER_PREFIX = "player-";
    /**
     * Obtains standard name for a player with a given id
     */
    public static final String getPlayerMindName(int playerId)
    {
        return PLAYER_PREFIX + playerId;
    }


    private Room room;
    private Hashtable minds;
    private RoomStateMatrix collisionMatrix;
    private EntityFactory entityFactory;
    private Device device;
    private int updates;

    public Engine(EntityFactory entityFactory, Device device)
    {
        this.minds = new Hashtable();
        this.collisionMatrix = new RoomStateMatrix(true);
        this.entityFactory = entityFactory;
        this.device = device;
    }

    public Device getDevice()
    {
        return this.device;
    }

    public int getUpdates()
    {
        return this.updates;
    }

    public EntityFactory getEntityFactory()
    {
        return this.entityFactory;
    }

    public Room getRoom()
    {
        return this.room;
    }

    public void clearRoom()
    {
        // SHUTUP SHUTUP SHUTUP!
        this.device.stopAll();
        this.room = null;
        this.collisionMatrix.setRoom(null);
        //this.stateMatrix.setRoom(room);
        this.updates = 0;
    }

    public void setRoom(Room room)
    {
        // clear room is always called before now
        //clearRoom();
        this.room = room;
        this.collisionMatrix.setRoom(room);
        if(this.room != null)
        {
            // load the collision matrix
            this.collisionMatrix.update();
            if(this.room.getMusic() != null)
            {
                this.device.play(this.room.getMusic(), Device.LOOP);
            }
        }
    }

    public void addEntity(Entity entity)
    {
        this.room.addEntity(entity);
        // add it to the matrix too
        this.collisionMatrix.addEntity(entity);
    }

    public void removeEntity(Entity entity)
    {
        this.room.removeEntity(entity);
        // remove it from the matrix too
        this.collisionMatrix.removeEntity(entity);
    }

    public RoomStateMatrix getStateMatrix()
    {
        return this.collisionMatrix;
    }

    /**
     * obtains predefined minds in the game, for example, the player 
     * minds
     */
    public Mind getMind(String name)
    {
        return (Mind)this.minds.get(name);
    }

    public void setMind(String name, Mind mind)
    {
        this.minds.put(name, mind);
    }

    public final void update()
    {
        this.updates ++;
        //this.stateMatrix.update();
        // update all the entities in the layers
        QuickVector layers = this.room.getLayers();
        for(int i=layers.size(); i>0; )
        {
            i--;
            Layer layer = (Layer)layers.elementAt(i);
            update(layer);
        }
        // do the collisions
        //this.collisionMatrix.update();
        for(int i=layers.size(); i>0; )
        {
            i--;
            Layer layer = (Layer)layers.elementAt(i);
            QuickVector entities = layer.getEntities();
            for(int j=entities.size(); j>0; )
            {
                j--;
                Entity entity = (Entity)entities.elementAt(j);

                Rectangle bounds = entity.bounds;
                Rectangle.expand(bounds, entity.xVelocity, entity.yVelocity, this.collisionRect);
                bounds = this.collisionRect;
                this.collisionMatrix.convertToTileBounds(bounds, this.tileRect);
                Rectangle tileBounds = this.tileRect;

                // simple collision handling
                doCollisions(entity, bounds, tileBounds, i, this.collisionVector);
                /*
                QuickVector collisions = getCollisions(entity, bounds, tileBounds, i, this.collisionVector);
                if(collisions != null)
                {
                    for(int k=0; k<collisions.size(); k++)
                    {
                        Collision collision;
                        collision = (Collision)collisions.elementAt(k);
                        CollisionData data = collision.getCollisionData(entity);
                        Entity compareEntity = data.with;
                        CollisionData compareData = collision.getCollisionData(compareEntity);
                        boolean compareResult = compareEntity.collision(this, compareData);
                        if(compareResult)
                        {
                            // remove and re-add the compare entity, shouldn't happen
                            // very much if ever
                            this.collisionMatrix.removeEntity(compareEntity);
                            this.collisionMatrix.addEntity(compareEntity);
                        }
                        boolean result = entity.collision(this, data);
                        if(result)
                        {
                            // adjust the collision matrix (maybe we should remove this?)
                            this.collisionMatrix.removeEntity(entity, tileBounds);
                            this.collisionMatrix.addEntity(entity);
                        }
                    }
                }
                */
                // do collisions
                /* because we only allow cardinal directions along defined paths, we don't
                   have to worry about strict collision detection
                int collisionCount = 0;
                while(collisions != null && collisionCount != MAX_COLLISIONS)
                {
                    for(int k=0; k<collisions.size(); k++)
                    {
                        Collision collision;
                        collision = (Collision)collisions.elementAt(k);
                        CollisionData data = collision.getCollisionData(entity);
                        Entity compareEntity = data.with;
                        CollisionData compareData = collision.getCollisionData(compareEntity);
                        boolean compareResult = compareEntity.collision(this, compareData);
                        if(compareResult)
                        {
                            // TODO : redo the collisions on this object

                            // remove and re-add the compare entity, shouldn't happen
                            // very much if ever
                            this.collisionMatrix.removeEntity(compareEntity);
                            this.collisionMatrix.addEntity(compareEntity);
                        }

                        boolean result = entity.collision(this, data);
                        if(result)
                        {
                            this.collisionMatrix.removeEntity(entity, tileBounds);
                            this.collisionMatrix.addEntity(entity);
                            bounds = entity.bounds;
                            Rectangle.expand(bounds, entity.xVelocity, entity.yVelocity, C_RECT);
                            bounds = C_RECT;
                            this.collisionMatrix.convertToTileBounds(bounds, T_RECT);
                            tileBounds = T_RECT;
                            collisions = getCollisions(entity, bounds, tileBounds, i, C_VECTOR);
                            break;
                        }
                    }
                    // TODO : we shouldn't need this
                    collisionCount++;
                }
                */
                // all collisions for this entity are done, can update it
                int dx = entity.xVelocity;
                int dy = entity.yVelocity;
                bounds = entity.bounds;
                bounds.setLocation(bounds.x + dx, bounds.y + dy);
                entity.direction = Directions.addDirection(entity.direction, entity.turnVelocity);
            }
        }
    }

    private QuickVector cached = new QuickVector();

    private final void doCollisions(Entity entity, Rectangle vBounds, Rectangle tileBounds, int layerId, QuickVector collisions)
    {
        this.cached.removeAllElements();
        QuickVector found = this.cached;
        for(int x=tileBounds.x; x<tileBounds.rightEdge; x++)
        {
            for(int y=tileBounds.y; y<tileBounds.bottomEdge; y++)
            {
                QuickVector entities = this.collisionMatrix.getEntitiesOnTile(x, y);
                if(entities != null)
                {
                    for(int l=entities.size(); l>0; )
                    {
                        l--;
                        Entity compareEntity = (Entity)entities.elementAt(l);
                        if(!found.contains(compareEntity))
                        {
                            found.addElement(compareEntity);
                        }
                    }
                }
            }
        }
        int pos = 0;
        for(int i=found.size(); i>0; )
        {
            i--;
            Entity compareEntity = (Entity)found.elementAt(i);
            if(compareEntity != entity && compareEntity.layerId <= layerId)
            {
                Collision cached;
                if(this.cachedCollisions.size() <= pos)
                {
                    cached = new Collision();
                    this.cachedCollisions.addElement(cached);
                }else{
                    cached = (Collision)this.cachedCollisions.elementAt(pos);
                }
                Collision c = CollisionDetector.getCollision(entity, vBounds, compareEntity, cached);
                if(c != null)
                {
                    pos++;
                    Collision collision;
                    collision = c;
                    CollisionData data = collision.getCollisionData(entity);
                    CollisionData compareData = collision.getCollisionData(compareEntity);
                    compareEntity.collision(this, compareData);
                    //if(compareResult)
                    {
                        // remove and re-add the compare entity, shouldn't happen
                        // very much if ever
                        //this.collisionMatrix.removeEntity(compareEntity);
                        //this.collisionMatrix.addEntity(compareEntity);
                    }
                    entity.collision(this, data);
                    //if(result)
                    {
                        // adjust the collision matrix (maybe we should remove this?)
                        //this.collisionMatrix.removeEntity(entity, tileBounds);
                        //this.collisionMatrix.addEntity(entity);
                    }

                }
            }
        }
    }

    private final QuickVector getEnumerationCollisions(QuickVector found, Entity entity, Rectangle vBounds, Rectangle tileBounds, int layerId, QuickVector collisions)
    {
        QuickVector result = null;
        for(int i=found.size(); i>0; )
        {
            i--;
            Entity compareEntity = (Entity)found.elementAt(i);
            if(compareEntity != entity && compareEntity.layerId <= layerId)
            {
                Collision cached;
                int pos;
                if(result == null)
                {
                    pos = 0;
                }else{
                    pos = collisions.size();
                }
                if(this.cachedCollisions.size() <= pos)
                {
                    cached = new Collision();
                    this.cachedCollisions.addElement(cached);
                }else{
                    cached = (Collision)this.cachedCollisions.elementAt(pos);
                }
                Collision c = CollisionDetector.getCollision(entity, vBounds, compareEntity, cached);
                if(c != null)
                {
                    // by removing the possibility of diagonal movement we
                    // no longer have to sort this (I think)
                    /*
                    if(result == null)
                    {
                        collisions.removeAllElements();
                        result = collisions;
                    }
                    */
                    //result.addElement(c);

                }
            }
        }
        return result;
    }

    private final QuickVector getCollisions2(Entity entity, Rectangle vBounds, Rectangle tileBounds, int layerId, QuickVector collisions)
    {
        QuickVector result = null;
        Hashtable cache = new Hashtable();
        cache.clear();
        for(int x=tileBounds.x; x<tileBounds.rightEdge; x++)
        {
            for(int y=tileBounds.y; y<tileBounds.bottomEdge; y++)
            {
                QuickVector entities = this.collisionMatrix.getEntitiesOnTile(x, y);
                if(entities != null)
                {
                    for(int l=entities.size(); l>0; )
                    {
                        l--;
                        Entity compareEntity = (Entity)entities.elementAt(l);
                        // check to make sure that this entity isn't already compared
                        if(compareEntity != entity && compareEntity.layerId <= layerId)
                        {
                            boolean compared = cache.containsKey(compareEntity);
                            /*
                            if(result != null)
                            {
                                for(int i=result.size(); i>0; )
                                {
                                    i--;
                                    Collision collision = (Collision)result.elementAt(i);
                                    if(collision.entity2 == compareEntity)
                                    {
                                        compared = true;
                                        break;
                                    }
                                }
                            }
                            */
                            if(!compared)
                            {
                                cache.put(compareEntity, compareEntity);
                                Collision cached;
                                int pos;
                                if(result == null)
                                {
                                    pos = 0;
                                }else{
                                    pos = collisions.size();
                                }
                                if(this.cachedCollisions.size() <= pos)
                                {
                                    cached = new Collision();
                                    this.cachedCollisions.addElement(cached);
                                }else{
                                    cached = (Collision)this.cachedCollisions.elementAt(pos);
                                }
                                Collision c = CollisionDetector.getCollision(entity, vBounds, compareEntity, cached);
                                if(c != null)
                                {
                                    // by removing the possibility of diagonal movement we
                                    // no longer have to sort this (I think)
                                    if(result == null)
                                    {
                                        collisions.removeAllElements();
                                        result = collisions;
                                    }
                                    collisions.addElement(c);
                                    /*
                                    boolean spotFound = false;
                                    if(result == null)
                                    {
                                        collisions.removeAllElements();
                                        result = collisions;
                                    }else{
                                        int cost = c.getCollisionData(entity).cost;
                                        CollisionData data = c.getCollisionData(entity);
                                        for(int m=0; m<collisions.size(); m++)
                                        {
                                            Collision found;
                                            found = (Collision)collisions.elementAt(m);
                                            CollisionData foundData = found.getCollisionData(entity);
                                            int foundCost = foundData.cost;
                                            if(foundCost > cost || foundCost == cost && foundData.length < data.length)
                                            {
                                                collisions.insertElementAt(c, m);
                                                spotFound = true;
                                                break;
                                            }
                                        }
                                    }
                                    if(!spotFound)
                                    {
                                        collisions.addElement(c);
                                    }
                                    */
                                }
                            }
                        }
                    }
                }
            }
        }

        return result;
    }
    /*
    private Vector getCollisions2(Entity entity, int layerId, Vector collisions)
    {
        Vector result = null;
        Vector layers = this.room.getLayers();
        for(int k=layerId; k>=0; k--)
        {
            Layer compareLayer = (Layer)layers.elementAt(k);
            Vector compareEntities = compareLayer.getEntities();
            for(int l=compareEntities.size(); l>0; )
            {
                l--;
                Entity compareEntity = (Entity)compareEntities.elementAt(l);
                if(compareEntity != entity)
                {
                    Collision cached;
                    int pos;
                    if(result == null)
                    {
                        pos = 0;
                    }else{
                        pos = collisions.size();
                    }
                    if(C_COLLISIONS.size() <= pos)
                    {
                        cached = new Collision();
                        C_COLLISIONS.addElement(cached);
                    }else{
                        cached = (Collision)C_COLLISIONS.elementAt(pos);
                    }
                    Collision c = CollisionDetector.getCollision(entity, compareEntity, cached);
                    if(c != null)
                    {
                        boolean spotFound = false;
                        if(result == null)
                        {
                            collisions.clear();
                            result = collisions;
                        }else{
                            int cost = c.getCollisionData(entity).cost;
                            CollisionData data = c.getCollisionData(entity);
                            for(int m=0; m<collisions.size(); m++)
                            {
                                Collision found;
                                found = (Collision)collisions.elementAt(m);
                                CollisionData foundData = found.getCollisionData(entity);
                                int foundCost = foundData.cost;
                                if(foundCost > cost || foundCost == cost && foundData.length < data.length)
                                {
                                    collisions.insertElementAt(c, m);
                                    spotFound = true;
                                    break;
                                }
                            }
                        }
                        if(!spotFound)
                        {
                            collisions.addElement(c);
                        }
                    }
                }
            }
        }
        return result;
    }
    */

    private final void update(Layer layer)
    {
        QuickVector entities = layer.getEntities();
        for(int i=entities.size(); i>0; )
        {
            i--;
            Entity entity = (Entity)entities.elementAt(i);
            update(entity);
            if(entity.isRemovable())
            {
                // get rid of it
                entities.removeElementAt(i);
                //this.getStateMatrix().removeEntity(entity);
            }
        }
    }

    private final void update(Entity entity)
    {
        try
        {
            entity.update(this);
        }catch(Exception ex){
            // TODO : a log?
            //System.err.println(ex);
            ex.printStackTrace();
        }
    }
}
